#include "OSGRender.h"
#include <sstream>
#include <osg/ShapeDrawable>
#include <osg/MatrixTransform>
#include <osgGA/OrbitManipulator>
#include <osgGA/StateSetManipulator>
#include <osgText/Text>
#include <osgDB/ReadFile>

#include "WaterImp.h"

class MyHandler : public osgGA::GUIEventHandler
{
    OSGRender* _render;
public:
    MyHandler(OSGRender* render) : _render(render) {}

protected:
    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override
    {
        switch (ea.getEventType())
        {
        case osgGA::GUIEventAdapter::FRAME: return handleFrame(ea, aa);
        case osgGA::GUIEventAdapter::PUSH: return handleMousePush(ea, aa);
        case osgGA::GUIEventAdapter::DRAG: return handleMouseDrag(ea, aa);
        case osgGA::GUIEventAdapter::RELEASE: return handleMouseRelease(ea, aa);
        case osgGA::GUIEventAdapter::KEYDOWN: return handleKeyDown(ea, aa);
        case osgGA::GUIEventAdapter::KEYUP: return handleKeyUp(ea, aa);
        case osgGA::GUIEventAdapter::RESIZE: return handleResize(ea, aa);
        default:
            break;
        }
        return false;
    }

private:
    bool handleFrame(const osgGA::GUIEventAdapter& /*ea*/, osgGA::GUIActionAdapter& /*aa*/) { return false; }
    bool handleMousePush(const osgGA::GUIEventAdapter& /*ea*/, osgGA::GUIActionAdapter& /*aa*/) { return false; }
    bool handleMouseDrag(const osgGA::GUIEventAdapter& /*ea*/, osgGA::GUIActionAdapter& /*aa*/) { return false; }
    bool handleMouseRelease(const osgGA::GUIEventAdapter& /*ea*/, osgGA::GUIActionAdapter& /*aa*/) { return false; }
    bool handleKeyDown(const osgGA::GUIEventAdapter& /*ea*/, osgGA::GUIActionAdapter& /*aa*/) { return false; }

    bool handleKeyUp(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& /*aa*/)
    {
        _render->handleKeyUp(ea.getKey());
        return false;
    }

    bool handleResize(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter&) 
    {
        _render->handleResize(ea.getWindowX(), ea.getWindowY(), ea.getWindowWidth(), ea.getWindowHeight());
        return false; 
    }

};

OSGRender::OSGRender()
{
    _winRect.set(200, 200, 800, 600);
    _clearColor.set(0.3f, 0.6f, 0.9f, 1);
}

OSGRender::~OSGRender()
{
}

void OSGRender::setWindowRect(int x, int y, int w, int h)
{
    _winRect.set(x, y, w, h);
}

void OSGRender::setClearColorf(float r, float g, float b)
{
    _clearColor.set(r, g, b, 1);
}

void OSGRender::setClearColorub(unsigned char r, unsigned char g, unsigned char b)
{
    _clearColor.set(r/255.0f, g/255.0f, b/255.0f, 1);
}

void OSGRender::init()
{
    osg::DisplaySettings::instance()->setNumMultiSamples(16);
    osg::DisplaySettings::instance()->setMinimumNumStencilBits(8);

    osg::ref_ptr<osg::Group> root = new osg::Group;
    _viewer.setSceneData(root);

    _scene = new osg::Switch;
    root->addChild(_scene);
    {
        osg::ref_ptr<osg::Program> program = new osg::Program;
        program->addShader(osgDB::readShaderFile(osg::Shader::VERTEX, "./shader/main_vs.glsl"));
        program->addShader(osgDB::readShaderFile(osg::Shader::FRAGMENT, "./shader/main_fs.glsl"));
        root->getOrCreateStateSet()->setAttributeAndModes(program);
    }

    _viewer.setUpViewInWindow(_winRect.x(), _winRect.y(), _winRect.z(), _winRect.w());
    _viewer.setCameraManipulator(new osgGA::OrbitManipulator(), false);
    _viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded);
    _viewer.addEventHandler(new osgGA::StateSetManipulator(_scene->getOrCreateStateSet()));

    osg::ref_ptr<osg::Camera> masterCamera = _viewer.getCamera();
    masterCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    masterCamera->setClearColor(_clearColor);
    masterCamera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);

    _viewer.addEventHandler(new MyHandler(this));

    osgViewer::ViewerBase::Windows windows;
    _viewer.getWindows(windows);
    for (auto w : windows)
    {
        w->getState()->setUseVertexAttributeAliasing(true);
        w->getState()->setUseModelViewAndProjectionUniforms(true);
    }

    _hudText = new osg::Camera;
    {
        _hudText->setClearMask(NULL);
        _hudText->setRenderOrder(osg::Camera::POST_RENDER);
        _hudText->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
        _hudText->setViewport(0, 0, _winRect[2], _winRect[3]);
        _hudText->setProjectionMatrixAsOrtho2D(0, _winRect[2], 0, _winRect[3]);
        _hudText->setViewMatrix(osg::Matrix::identity());
        _hudText->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);

        osg::ref_ptr<osgText::Text> text = new osgText::Text;
        text->setText(
            "Text Infomation.\n"
        );
        text->setAlignment(osgText::Text::LEFT_BOTTOM);
        text->setPosition(osg::Vec3(5, 5, 0));
        text->setFont("arial.ttf");
        text->setColor(osg::Vec4(1, 1, 1, 1));
        text->setCharacterSize(15.0f);
        _hudText->addChild(text);
        _text = text;
    }
    root->addChild(_hudText);

    root->addChild(osgDB::readNodeFile("axes.osgt"));

    _scene->addChild(createWaterNormalMap());

    _scene->addChild(createWater(20, 10, 0.7));

    _scene->addChild(createWater(20, 20, 0.5));

    _scene->setSingleChildOn(0);
}

void OSGRender::run()
{
    _viewer.home();
    _viewer.run();
}

void OSGRender::updateText(const std::string & msg)
{
    if (_text.valid()) 
    {
        _text->setText(msg);
        _text->dirtyGLObjects();
    }
}

// handle key up event
void OSGRender::handleKeyUp(int key)
{
    if (key == 'z') 
    {
        const auto& valueList = _scene->getValueList();
        int i = 0;
        int count = valueList.size();
        for (; i<count; ++i) 
        {
            if (valueList[i]) {
                break;
            }
        }
        int index = (i + 1) % count;
        _scene->setSingleChildOn(index);
        
        std::stringstream ss;
        ss << "Current Scene = " << index << std::endl;
        updateText(ss.str());
    }
    else 
    {

    }
}

// resize window
void OSGRender::handleResize(int x, int y, int w, int h)
{
    if (_hudText.valid() && (_winRect[2]!=w || _winRect[3]!=h)) 
    {
        _hudText->setProjectionMatrixAsOrtho2D(0, w, 0, h);
        _hudText->setViewport(0, 0, w, h);
    }



    _winRect.set(x, y, w, h);
}
